package io.light.frame.dal.mybatis.generator.core.mapper.type;

import io.light.frame.dal.mybatis.generator.core.cfg.ModuleConfiguration;
import io.light.frame.dal.mybatis.generator.core.domain.clazz.Clazz;
import io.light.frame.dal.mybatis.generator.core.domain.mapper.Mapper;
import io.light.frame.dal.mybatis.generator.core.exceptions.MybatisGenException;
import io.light.frame.dal.mybatis.generator.core.meta.entity.Table;
import io.light.frame.dal.mybatis.generator.core.meta.entity.TableColumn;
import org.apache.ibatis.type.JdbcType;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static org.apache.ibatis.type.JdbcType.*;

/**
 * Data type handler
 *
 * @author Ivan
 * @version 1.0.0
 * @date 2021-06-15 00:04
 */
public class DefaultTypeHandler implements TypeHandler {

    /**
     * {@link JdbcType} -> java {@link Class} mapping
     */
    private final Map<JdbcType, Clazz> typeMapping = new HashMap<>();

    {
        register(String.class, CHAR, VARCHAR, CLOB, LONGVARCHAR);
        register(Integer.class, TINYINT, SMALLINT, INTEGER);
        register(Long.class, BIGINT);
        register(Float.class, FLOAT);
        register(Double.class, DOUBLE);
        register(BigDecimal.class, DECIMAL, NUMERIC);
        register(Float.class, FLOAT);
        register(Boolean.class, BIT);
        register(Date.class, TIME, TIMESTAMP, DATE);
        register(byte[].class, BINARY, VARBINARY, BLOB);
    }

    @Override
    public void handle(Mapper mapper, ModuleConfiguration configuration) {
        for (Mapper.Property property : mapper.getProperties()) {
            property.setJdbcType(JdbcType.forCode(property.getColumn().getTypeCode()));
            Clazz mappedJavaType = mapJavaType(property, configuration);
            if (mappedJavaType != null) {
                property.setJavaType(mappedJavaType);
                return;
            }
            Table table = property.getTable();
            TableColumn column = property.getColumn();
            JdbcType jdbcType = property.getJdbcType();
            Clazz javaType = null;
            if (!CollectionUtils.isEmpty(configuration.getTypeMapping())) {
                Class<?> javaClass = configuration.getTypeMapping().get(jdbcType);
                if (javaClass != null) {
                    javaType = Clazz.of(javaClass);
                }
            }
            if (javaType == null) {
                javaType = typeMapping.get(jdbcType);
            }
            if (javaType == null) {
                throw new MybatisGenException(String.format("Unsupported jdbc type: %s, table: %s, column: %s",
                        jdbcType.name(), table.getName(), column.getName()));
            }
            property.setJavaType(javaType);
        }
    }

    @Override
    public Clazz mapJavaType(Mapper.Property property, ModuleConfiguration configuration) {
        TableColumn column = property.getColumn();
        JdbcType jdbcType = property.getJdbcType();
        // map tinyint
        if (jdbcType == TINYINT || column.getTypeName().toLowerCase().startsWith(TINYINT.name().toLowerCase())) {
            if (column.getLength() != null && column.getLength() == 1) {
                if (configuration.isTinyintCanAsBoolean()) {
                    property.setJdbcType(BIT);
                    return Clazz.of(Boolean.class);
                } else {
                    property.setJdbcType(TINYINT);
                    return Clazz.of(Integer.class);
                }
            }
        }
        return null;
    }

    @Override
    public void register(JdbcType jdbcType, Class<?> javaType) {
        if (jdbcType == null || javaType == null) {
            return;
        }
        register(jdbcType, Clazz.of(javaType));
    }

    @Override
    public void register(JdbcType jdbcType, Clazz javaType) {
        if (jdbcType == null || javaType == null) {
            return;
        }
        typeMapping.put(jdbcType, javaType);
    }

    @Override
    public void register(Class<?> javaType, JdbcType... jdbcTypes) {
        if (jdbcTypes == null || jdbcTypes.length == 0) {
            return;
        }
        register(Clazz.of(javaType), jdbcTypes);
    }

    @Override
    public void register(Clazz javaType, JdbcType... jdbcTypes) {
        if (jdbcTypes == null || jdbcTypes.length == 0) {
            return;
        }
        for (JdbcType jdbcType : jdbcTypes) {
            register(jdbcType, javaType);
        }
    }

}
